package myredispool

import (
	"errors"
	"fmt"
	"strconv"
	"sync"

	"github.com/garyburd/redigo/redis"
)

type PoolConf struct {
	Net       string
	IpAddress string
	MaxConn   int32
	MinConn   int32

	idleConn       int32
	createdConnNum int32
	pool           *sync.Pool

	mu    sync.Mutex
	once  sync.Once
	isErr bool
}

var p PoolConf

func (p *PoolConf) Show() {
	fmt.Println(p.createdConnNum, "-----", p.MaxConn, "-----", p.idleConn)
}

func (p *PoolConf) createdConnSub() error {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.createdConnNum--
	err := p.ensureAdequateMinConnection()
	return err
}

func newFunc() interface{} {
	c, err := redis.Dial(p.Net, p.IpAddress)
	if err != nil {
		return err
	}
	return c
}

func Init(pp *PoolConf) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	poolInit := func() {
		if pp.Net == "" || pp.IpAddress == "" || pp.MaxConn <= 0 || pp.MinConn < 0 || pp.MaxConn < pp.MinConn {
			p.isErr = true
			return
		}
		p.Net = pp.Net
		p.IpAddress = pp.IpAddress
		p.MaxConn = pp.MaxConn
		p.MinConn = pp.MinConn
		p.pool = &sync.Pool{New: newFunc}

		var i int32
		for i = 0; i < p.MinConn; i++ {
			c := p.pool.Get()
			switch c.(type) {
			case error:
				p.isErr = true
				return
			}
			p.pool.Put(c)
			p.idleConn++
			p.createdConnNum++
		}
	}

	p.once.Do(poolInit)
	if p.isErr {
		return errors.New("get conn error in Init()")
	}
	return nil
}

func (p *PoolConf) ensureAdequateMinConnection() error {
	if p.createdConnNum < p.MinConn {
		for i := p.createdConnNum; i <= p.MinConn; i++ {
			c := p.pool.Get()
			switch v := c.(type) {
			case error:
				return v
			}
			p.pool.Put(c)
			p.idleConn++
			p.createdConnNum++
		}
	}
	return nil
}

func (p *PoolConf) getConn() (redis.Conn, error) {
	p.mu.Lock()
	defer p.mu.Unlock()

	var c interface{}
	//判断是否剩余的连接
	if p.idleConn > 0 {
		p.idleConn--
		c = p.pool.Get()
	} else {
		//无剩余连接就看已经创建的数量是否达到最大值
		if p.createdConnNum >= p.MaxConn {
			return nil, errors.New("The max connection is reached")
		} else {
			//没有达到最大连接的时候
			p.createdConnNum++
			c = p.pool.Get()
		}
	}
	//取出连接时看一下是否是空的连接要是空的就从已创建的连接里删掉然后返回一个error
	switch v := c.(type) {
	case error:
		p.createdConnNum--
		//删掉一个已创建的连接后，判断createdConn是否小于minConn
		err := p.ensureAdequateMinConnection()
		if err != nil {
			return nil, err
		}
		return nil, v
	case redis.Conn:
		return v, nil
	default:
		panic("getConn error")
	}
	return nil, errors.New("the max connection is reached")
} //这样一系列的操作之后就保证了外面调用的函数不用考虑很多，只去取连接就可以

func (p *PoolConf) putConn(c redis.Conn) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.idleConn++
	p.pool.Put(c)
}

func SetUserFreq(userId string, offer map[string]int) error {
	if userId == "" || offer == nil {
		return errors.New("The argument of the SetUserFreq(string,string,string) is error")
	}
	c, gerr := p.getConn()
	if gerr != nil {
		return gerr
	}

	for offerId, count := range offer {
		if offerId == "" {
			return errors.New("offerId is nil")
		}
		err := c.Send("HINCRBY", userId, offerId, count)
		if err != nil {
			c.Close()
			cerr := p.createdConnSub()
			if cerr != nil {
				return cerr
			}
			return err
		}
	}
	ferr := c.Flush()
	if ferr != nil {
		c.Close()
		//------------------------------
		cerr := p.createdConnSub()
		if cerr != nil {
			return cerr
		}
		//------------------------------
		return ferr
	}
	p.putConn(c)
	return nil
}

func GetUserFreq(userId string) (map[string]int, error) {
	if userId == "" {
		return nil, errors.New("The argument of the GetUserFreq() is error")
	}

	c, gerr := p.getConn()
	if gerr != nil {
		return nil, gerr
	}
	params, err := redis.Strings(c.Do("HGETALL", userId))
	if err != nil {
		c.Close()
		//-------------------------------------
		cerr := p.createdConnSub()
		if cerr != nil {
			return nil, cerr
		}
		//-------------------------------------
		return nil, err
	}
	p.putConn(c)

	infomap := make(map[string]int)
	for i := 0; i < len(params); i += 2 {
		count, err := strconv.Atoi(params[i+1])
		if err != nil {
			return nil, err
		}
		infomap[params[i]] = count
	}
	return infomap, nil
}
